Passed
Branch master (6eb04c)
by Rafael S.
01:12
created

ieee754-buffer.js ➔ unpack   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 35
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 24
c 1
b 0
f 0
nc 8
nop 4
dl 0
loc 35
rs 8.3706
1
/*
2
 * Copyright (c) 2018 Rafael da Silva Rocha.
3
 * Copyright (c) 2013 DeNA Co., Ltd.
4
 * Copyright (c) 2010, Linden Research, Inc
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining
7
 * a copy of this software and associated documentation files (the
8
 * "Software"), to deal in the Software without restriction, including
9
 * without limitation the rights to use, copy, modify, merge, publish,
10
 * distribute, sublicense, and/or sell copies of the Software, and to
11
 * permit persons to whom the Software is furnished to do so, subject to
12
 * the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be
15
 * included in all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
 *
25
 */
26
27
/**
28
 * @fileoverview Functions to pack and unpack IEEE 754 floating point numbers.
29
 * @see https://github.com/rochars/ieee754-buffer
30
 */
31
32
/** @module ieee754Buffer */
33
34
/**
35
 * Pack a IEEE 754 floating point number.
36
 * Derived from typedarray.js by Linden Research, MIT License.
37
 * @see https://bitbucket.org/lindenlab/llsd/raw/7d2646cd3f9b4c806e73aebc4b32bd81e4047fdc/js/typedarray.js
38
 * @param {!Uint8Array|!Array<number>} buffer The buffer.
39
 * @param {number} index The index to write on the buffer.
40
 * @param {number} num The number.
41
 * @param {number} ebits The number of bits of the exponent.
42
 * @param {number} fbits The number of bits of the fraction.
43
 * @return {number} The next index to write on the buffer.
44
 */
45
export function pack(buffer, index, num, ebits, fbits) {
46
  /** @type {number} */
47
  let bias = (1 << (ebits - 1)) - 1;
48
  // Round overflows
49
  if (Math.abs(num) > Math.pow(2, bias + 1) - ((ebits + fbits) * 2)) {
50
    num = num < 0 ? -Infinity : Infinity;
51
  }
52
  /**
53
   * sign, need this to handle negative zero
54
   * @see http://cwestblog.com/2014/02/25/javascript-testing-for-negative-zero/
55
   * @type {number}
56
   */
57
  let sign = (((num = +num) || 1 / num) < 0) ? 1 : num < 0 ? 1 : 0;
58
  num = Math.abs(num);
59
  /** @type {number} */
60
  let exp = Math.min(Math.floor(Math.log(num) / Math.LN2), 1023);
61
  /** @type {number} */
62
  let fraction = roundToEven(num / Math.pow(2, exp) * Math.pow(2, fbits));
63
  // NaN
64
  if (num !== num) {
65
    fraction = Math.pow(2, fbits - 1);
66
    exp = (1 << ebits) - 1;
67
  // Number
68
  } else if (num !== 0) {
69
    if (num >= Math.pow(2, 1 - bias)) {
70
      if (fraction / Math.pow(2, fbits) >= 2) {
71
        exp = exp + 1;
72
        fraction = 1;
73
      }
74
      // Overflow
75
      if (exp > bias) {
76
        exp = (1 << ebits) - 1;
77
        fraction = 0;
78
      } else {
79
        exp = exp + bias;
80
        fraction = roundToEven(fraction) - Math.pow(2, fbits);
81
      }
82
    } else {
83
      fraction = roundToEven(num / Math.pow(2, 1 - bias - fbits));
84
      exp = 0;
85
    } 
86
  }
87
  return packFloatBits_(buffer, index, ebits, fbits, sign, exp, fraction);
88
}
89
90
/**
91
 * Unpack a IEEE 754 floating point number.
92
 * Derived from IEEE754 by DeNA Co., Ltd., MIT License. 
93
 * Adapted to handle NaN. Should port the solution to the original repo.
94
 * @see https://github.com/kazuho/ieee754.js/blob/master/ieee754.js
95
 * @param {!Uint8Array|!Array<number>} buffer The buffer.
96
 * @param {number} index The index to read from the buffer.
97
 * @param {number} ebits The number of bits of the exponent.
98
 * @param {number} fbits The number of bits of the fraction.
99
 * @return {number} The floating point number.
100
 */
101
export function unpack(buffer, index, ebits, fbits) {
102
  let exponentBias = (1 << (ebits - 1)) - 1;
103
  let numBytes = Math.ceil((ebits + fbits) / 8);
104
  /** @type {number} */
105
  let eMax = (1 << ebits) - 1;
106
  /** @type {number} */
107
  let bias = Math.pow(2, -(8 * numBytes - 1 - ebits));
108
  /** @type {number} */
109
  let significand;
110
  /** @type {string} */
111
  let leftBits = "";
112
  for (let i = numBytes - 1; i >= 0 ; i--) {
113
    /** @type {string} */
114
    let t = buffer[i + index].toString(2);
115
    leftBits += "00000000".substring(t.length) + t;
116
  }
117
  /** @type {number} */
118
  let sign = leftBits.charAt(0) == "1" ? -1 : 1;
119
  leftBits = leftBits.substring(1);
120
  /** @type {number} */
121
  let exponent = parseInt(leftBits.substring(0, ebits), 2);
122
  leftBits = leftBits.substring(ebits);
123
  if (exponent == eMax) {
124
    if (parseInt(leftBits, 2) !== 0) {
125
      return NaN;
126
    }
127
    return sign * Infinity;  
128
  } else if (exponent == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing exponent to 0 using the == operator is not safe. Consider using === instead.
Loading history...
129
    exponent += 1;
130
    significand = parseInt(leftBits, 2);
131
  } else {
132
    significand = parseInt("1" + leftBits, 2);
133
  }
134
  return sign * significand * bias * Math.pow(2, exponent - exponentBias);
135
}
136
137
/**
138
 * Pack a IEEE754 from its sign, exponent and fraction bits
139
 * and place it in a byte buffer.
140
 * @param {!Uint8Array|!Array<number>} buffer The byte buffer to write to.
141
 * @param {number} index The buffer index to write.
142
 * @param {number} ebits The number of bits of the exponent.
143
 * @param {number} fbits The number of bits of the fraction.
144
 * @param {number} sign The sign.
145
 * @param {number} exp the exponent.
146
 * @param {number} fraction The fraction.
147
 * @return {number}
148
 * @private
149
 */
150
function packFloatBits_(buffer, index, ebits, fbits, sign, exp, fraction) {
151
  /** @type {!Array<number>} */
152
  let bits = [];
153
  // the sign
154
  bits.push(sign);
155
  // the exponent
156
  for (let i = ebits; i > 0; i -= 1) {
157
    bits[i] = (exp % 2 ? 1 : 0);
158
    exp = Math.floor(exp / 2);
159
  }
160
  // the fraction
161
  let len = bits.length;
162
  for (let i = fbits; i > 0; i -= 1) {
163
    bits[len + i] = (fraction % 2 ? 1 : 0);
164
    fraction = Math.floor(fraction / 2);
165
  }
166
  // pack as bytes
167
  /** @type {string} */
168
  let str = bits.join('');
169
  /** @type {number} */
170
  let numBytes = Math.floor((ebits + fbits + 1) / 8) + index - 1;
171
  /** @type {number} */
172
  let k = index;
173
  while (numBytes >= index) {
174
    buffer[numBytes] = parseInt(str.substring(0, 8), 2);
175
    str = str.substring(8);
176
    numBytes--;
177
    k++;
178
  }
179
  return k;
180
}
181
182
function roundToEven(n) {
183
  var w = Math.floor(n), f = n - w;
184
  if (f < 0.5)
185
    return w;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
186
  if (f > 0.5)
187
    return w + 1;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
188
  return w % 2 ? w + 1 : w;
189
}
190